'use strict';

//import { createTextureStorage2D, bindTexture2D, createShader } from './webgl_util';
//import { getMaskInfo } from "./mask/index";

// 程序对象（Program Object）是一个包含顶点着色器和片段着色器的对象，它们在GPU上执行以渲染图形。

// 属性是与顶点数据相关的变量。每个顶点可以有不同的属性值，例如位置、颜色、纹理坐标等。
// 属性通常用于将顶点数据从CPU传递到GPU。
// 在着色器代码中，属性变量使用attribute关键字声明。

// 统一变量是与片段着色器相关的变量。它们通常用于控制着色器的行为，
// 它们用于传递在整个渲染过程中保持不变的数据，例如颜色、透明度、光照等。

// function createModel(gl, vertexSource, fragmentSource) {
//     // 创建了一个WebGL程序对象,该对象是着色器链接后的产物，用于在GPU上执行着色器代码。
//     const program = gl.createProgram();
//     // 创建顶点着色器
//     const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexSource);
//     // 片段着色器
//     const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentSource);
//     // 将着色器附加到程序对象
//     gl.attachShader(program, vertexShader);
//     gl.attachShader(program, fragmentShader);
//     // 链接程序对象
//     gl.linkProgram(program);
//     if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
//         throw new Error(gl.getProgramInfoLog(program));
//     }
//     // 创建包装对象
//     const wrapper = { program };
//     // 获取程序对象中的所有活动属性，并将它们的名称和位置存储在包装对象中。
//     const numAttributes = gl.getProgramParameter(program, gl.ACTIVE_ATTRIBUTES);
//     for (let i = 0; i < numAttributes; i++) {
//         const attribute = gl.getActiveAttrib(program, i);
//         wrapper[attribute.name] = gl.getAttribLocation(program, attribute.name);
//     }
//     // 获取程序对象中的所有活动统一变量，并将它们的名称和位置存储在包装对象中。
//     const numUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
//     for (let i = 0; i < numUniforms; i++) {
//         const uniform = gl.getActiveUniform(program, i);
//         wrapper[uniform.name] = gl.getUniformLocation(program, uniform.name);
//     }
//     // 返回包含程序对象及其属性和统一变量位置的包装对象。
//     return wrapper;
// }

// 通过生成随机的十六进制字符串并将其组合成标准的UUID格式，实现了生成UUID的功能。
// UUID通常用于唯一标识信息，确保在分布式系统中不会发生冲突。
function uuid() {
    function s4() {
        // eslint-disable-next-line no-bitwise
        return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
    }
    return (s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4());
}
interface Grid {
    startX: number;//网格的起始 X 坐标。
    startY: number;//网格的起始 Y 坐标
    endX: number;//endX: number;
    endY: number;//endY: number;
    incrementX: number;//网格在 X 方向上的增量。
    incrementY: number;//网格在 Y 方向上的增量。
    width: number;//网格的宽度。
    height: number;//网格的高度。
    grids: Float32Array;//网格的数据，通常用于存储网格的值。
    invalid: number;//网格中的无效值。
    dimension: number;//表示网格的维度。
}
// eslint-disable-next-line no-unused-vars
export class GridLayer {
    #id: string;//层的唯一标识符。
    #grid: Grid;//网格数据。
    #legend: Map<number, number[]>;//颜色图例，用于将数值映射到颜色。
    #options: any;//层的配置选项。
    #models: any;//存储模型的对象。
    gl: WebGL2RenderingContext;//WebGL2 渲染上下文。
    #maskInfo: any;//掩膜信息。
    map: any;//地图对象。
    #drawModel: any;//绘制模型。
    #gridVao: any;//网格顶点数组对象。
    #elementType: any;// 元素类型。
    #vertexCount: any;//顶点数量。
    #gridTexture: any;//网格纹理。

    // 始化 GridLayer 类的实例
    constructor(grid: Grid, legend: Map<number, number[]>, options: any) {
        this.#id = (options.id) ? options.id : uuid();
        this.#grid = grid;
        this.#legend = legend;
        this.#options = options;
        this.#models = {};
    }
    //提供对 id, type, renderingMode, mask, opacity 等属性的访问和修改。
    // set mask 和 set opacity 方法在修改属性时会触发地图的重绘。
    get id() {
        return this.#id;
    }
    get type() {
        return 'custom';
    }
    get renderingMode() {
        return '2d';
    }
    get mask() {
        return this.#options.mask;
    }
    // 重置裁剪区域
    set mask(mask) {
        this.#options.mask = mask;
        const gl = this.gl;
        if (mask) {
            this.#maskInfo = getMaskInfo(this.mask, gl);
            this.#models['mask'] = this.#maskInfo.model;
        } else {
            let model = this.#models['mask'];
            if (model) {
                model.destroy();
                delete this.#models['mask'];
                this.#maskInfo = null;
            }

        }
        this.map.triggerRepaint();
    }
    get opacity():number{
        return this.#options.opacity;
    }
    set opacity(o:number){
        this.#options.opacity = o;
        if(this.gl){
            const model = this.#drawModel;
            this.gl.useProgram(model.program);
            // 绑定计算所需地理变量
            this.gl.uniform1f(model.u_opacity, this.opacity);
            this.map.triggerRepaint();
        }
    }
    // loader 方法用于加载新的网格数据，并更新相关的 WebGL 统一变量和纹理。
    loader(grid:Grid){
        this.#grid = grid;
        if(this.gl){
            const gl = this.gl;
            const model = this.#drawModel;
            gl.useProgram(model.program);
            // 绑定计算所需地理变量
            gl.uniform2fv(model.start, [grid.startX, grid.startY]);
            gl.uniform2fv(model.end, [grid.endX, grid.endY]);
            this.#gridTexture = this.gridFloatTexture(grid);
            this.map.triggerRepaint();
        }
    }
    // setBreakColorUniform 方法将颜色图例转换为 WebGL 统一变量。
    setBreakColorUniform(gl: any, program: any, normalizationColorMap: Map<number, number[]>) {
        // 真实值生成的颜色渐变区间
        let stops = [], colors = [];
        // 先提取key
        for (let [key] of normalizationColorMap) {
            stops.push(key);
        }
        // key根据sort从小到大排序
        stops = stops.sort(function (a, b) { return +(a) - +(b); });
        for (let key of stops) {
            let value = normalizationColorMap.get(key);
            let alpha;
            if (value[3] !== undefined)
                alpha = value[3];
            else
                alpha = 0.75;
            colors.push(value[0] / 255.0);
            colors.push(value[1] / 255.0);
            colors.push(value[2] / 255.0);
            colors.push(alpha);
        }
        // 传入数组
        gl.useProgram(program);
        const stopsLocation = gl.getUniformLocation(program, 'u_stops');
        const colorsLocation = gl.getUniformLocation(program, 'u_colors');
        gl.uniform1fv(stopsLocation, stops);
        gl.uniform4fv(colorsLocation, colors);
    }
    //创建并编译顶点和片段着色器。
    _createModel() {
        const vs = `#version 300 es
        layout(location=0) in vec2 a_position;
        uniform mat4 u_worldViewProjection;
        out vec2 v_position;
       
        void main() {
            gl_Position = u_worldViewProjection*vec4(a_position,0.0, 1.0);
            v_position = a_position;
        }`;
        const size = this.#legend.size;
        const fs = `#version 300 es
        precision highp float;
        #define M_PI 3.141592653589793 

        uniform vec2 start;
        uniform vec2 end;
      
        uniform float u_stops[${size}];
        uniform vec4 u_colors[${size}];

        uniform float u_opacity;

        uniform sampler2D u_gridTexture;
      
        in vec2 v_position;
        out vec4 outColor;

        // 墨卡托转经纬度
        vec2 mkt2lonlat(vec2 mkt){
            float lon = mkt.x * 360.0 - 180.0;
            float y2 = 180.0 - mkt.y * 360.0;
            float lat =  360.0 / M_PI * atan(exp(y2 * M_PI / 180.0)) - 90.0;
            return vec2(lon,lat);
        }
    
        void main() {
            int lastIndex = ${size} - 1;


            // 把墨卡托坐标转换成经纬度
            vec2 lonlat = mkt2lonlat(v_position);
            vec2 v_uv = (lonlat - start)/(end - start);
            float val = texture(u_gridTexture, v_uv).r;

            // 将浮点型转0-1坐标系
            // 大于大值
            if(val>=u_stops[lastIndex])
                outColor = vec4(u_colors[lastIndex].rgb*u_colors[lastIndex].a*u_opacity,u_colors[lastIndex].a*u_opacity);
            // 小于小值
            else if(val<u_stops[0])
                outColor = vec4(u_colors[0].rgb*u_colors[0].a*u_opacity,u_colors[0].a*u_opacity);
            else {
                for(int i=0;i<lastIndex;i++){
                    if(val>=u_stops[i]&&val<u_stops[i+1]){
                        // 预乘阿尔法
                        outColor = vec4(u_colors[i].rgb*u_colors[i].a*u_opacity,u_colors[i].a*u_opacity);
                        break;
                    } 
                }
            }
        }`;
        this.#drawModel = createModel(this.gl, vs, fs);
    }
    // 创建顶点数组对象（VAO）并绑定顶点数据。
    _createVao() {
        const gl = this.gl;
        const minx = this.#grid.startX;
        const miny = Math.min(this.#grid.startY, this.#grid.endY);
        const maxx = this.#grid.endX;
        const maxy = Math.max(this.#grid.startY, this.#grid.endY);
        const coordinates = [
            minx, miny,
            minx, maxy,
            maxx, maxy,
            maxx, miny,
        ];
        const pos = new Float32Array(coordinates.length);
        for (let i = 0; i < coordinates.length; i = i + 2) {
            // eslint-disable-next-line no-undef
            const coors = mapboxgl.MercatorCoordinate.fromLngLat({ lng: coordinates[i], lat: coordinates[i + 1] }, 0);
            pos[i] = coors.x;
            pos[i + 1] = coors.y;
        }
        const indices = [
            1, 0, 3,
            1, 3, 2,
        ];
        // //////////////////////////////
        //  SET UP 顶点
        // //////////////////////////////
        this.#gridVao = gl.createVertexArray();
        gl.bindVertexArray(this.#gridVao);

        const positionBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
        gl.bufferData(gl.ARRAY_BUFFER, pos, gl.STATIC_DRAW);
        gl.enableVertexAttribArray(0);
        gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);

        const indicesBuffer = gl.createBuffer();
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
        gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint8Array(indices), gl.STATIC_DRAW);
        this.#elementType = gl.UNSIGNED_BYTE;
        this.#vertexCount = indices.length;

        gl.bindBuffer(gl.ARRAY_BUFFER, null);
        gl.bindVertexArray(null);
    }
    // 设置 WebGL 统一变量。
    _createUniform() {
        const gl = this.gl;
        const grid = this.#grid;
        const gridModel = this.#drawModel;
        gl.useProgram(gridModel.program);
        // 绑定计算所需地理变量
        gl.uniform2fv(gridModel.start, [grid.startX, grid.startY]);
        gl.uniform2fv(gridModel.end, [grid.endX, grid.endY]);
        gl.uniform1f(gridModel.u_opacity, this.opacity);
        // 绑定数据纹理
        this.setBreakColorUniform(gl, gridModel.program, this.#legend);

        this.#gridTexture = this.gridFloatTexture(grid);
    }
    // 创建网格浮点型纹理。
    gridFloatTexture(grid: Grid) {
        const gl = this.gl;
        const texture = createTextureStorage2D(gl, {
            width: grid.width,
            height: grid.height,
            data: grid.grids,
            internalFormat: gl.RED, //webgl使用格式
            srcFormat: gl.R32F, //输入数据格式 
            type: gl.FLOAT,
            //声明该设置，否则会报错 Texture format does not support mipmap generation
            pixelStore: {
                [gl.UNPACK_ALIGNMENT]: 1,
                [gl.UNPACK_FLIP_Y_WEBGL]: 0
            },
            parameters: {
                [gl.TEXTURE_MAG_FILTER]: gl.LINEAR,
                [gl.TEXTURE_MIN_FILTER]: gl.LINEAR,
                [gl.TEXTURE_WRAP_S]: gl.CLAMP_TO_EDGE,
                [gl.TEXTURE_WRAP_T]: gl.CLAMP_TO_EDGE
            },
            levels: 1
        });
        return texture;
    }
    // 初始化方法，创建 WebGL 资源。
    initialize() {
        if (!this.map) { return; }
        this._createModel();
        this._createVao();
        this._createUniform();
    }
    //网格层被添加到地图时调用，初始化相关设置。
    onAdd(map, gl) {
        this.map = map;
        this.gl = gl;
        // 为了支持浮点型纹理过滤
        if (!gl.getExtension('OES_texture_float_linear')) {
            console.error('OES_texture_float_linear not available');
        }
        this.mask = this.#options.mask;
        this.initialize();
    }
    //负责在每一帧中渲染网格层。
    render(gl, matrix) {
        // 绘制掩膜
        if (this.mask)
            this.#maskInfo.draw(gl, matrix);

        const model = this.#drawModel;
        gl.useProgram(model.program);
        // 设置unifrom
        gl.uniformMatrix4fv(model.u_worldViewProjection, false, matrix);

        bindTexture2D(gl, this.#gridTexture, 11);
        gl.uniform1i(model.u_gridTexture, 11);
        // 绑定顶点vao
        gl.bindVertexArray(this.#gridVao);
        gl.drawElements(gl.TRIANGLES, this.#vertexCount,this.#elementType, 0);
        if (this.mask) {
            let context = this.map.painter.context;
            this.map.painter.currentStencilSource = undefined;
            context.setStencilMode(this.map.painter.stencilClearMode);
            gl.disable(gl.STENCIL_TEST);
        }
        gl.bindVertexArray(null);
    }
}